package risoe.syslab.fhnew.gui.widgets;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.svg.SVGDocument;
import risoe.syslab.fhnew.gui.VisualizationUpdate;
import risoe.syslab.fhnew.util.TimeSeries;
import risoe.syslab.model.CompositeMeasurement;

/**
 * Widget for use with SVG Visualizations. Adapted from FlexhouseVisSVG
 * @author M. Svendsen
 */
public class TimePlot {
	private Element mLine;
	private Element mLastValue;
	private Element mXTicks[];
	private Element mYTicks[];
	private final String mName;

	// Dimensions
	private double height_px;
	private double width_px;
	private double x_0;
	private double y_0;

	// Data
	private TimeSeries mData;
	private ArrayList<double[]> mPos;
	
	// Formatting
	private static final SimpleDateFormat mTimeFormat = new SimpleDateFormat("HH:mm");
	private static final DecimalFormat mYAxisFormat = new DecimalFormat("#.##");
	private static final DecimalFormat mDataFormat = new DecimalFormat("#.#");
	private static final Pattern mXYMarkPattern = Pattern.compile("([xy])mark([0-9]+)");


	/**
	 * Creates a new TimePlot widget. In order to use this widget, the SVG file must contain the
	 * appropriate elements (Can be copied from widgets.svg)
	 * @param name the name of the visualization that contains this TimePlot
	 * @param id the id of the main element in the svg file
	 * @param length the maximum timeframe of the plot
	 * @param document a SVG Document representing the SVG file used for this visualization
	 */
	public TimePlot(String name, String id, int length, SVGDocument document) {
		mName = name;
		mData = new TimeSeries(length);
		mPos = new ArrayList<double[]>();
		mXTicks = new Element[4];
		mYTicks = new Element[4];

		Element main = document.getElementById(id);
		NodeList nodes = main.getChildNodes();
		for (int i=0;i<nodes.getLength();i++) {
			if(!nodes.item(i).getNodeName().contains("#")) {
				Element element = (Element) nodes.item(i);
				if (element.getAttribute("id").contains("line")){
					mLine = element;
				}
				else if (element.getAttribute("id").contains("value")){
					mLastValue = element;
				}
				else if (element.getAttribute("id").contains("axes")) {
					width_px = Math.abs(Double.parseDouble(element.getAttribute("d").split(" +")[2].split(",")[0]));
					height_px = Math.abs(Double.parseDouble(element.getAttribute("d").split(" +")[3].split(",")[1]));
					y_0 = (Double.parseDouble(element.getAttribute("d").split(" +")[1].split(",")[1]));
					x_0 = (Double.parseDouble(element.getAttribute("d").split(" +")[1].split(",")[0]))-width_px;
				}
				else if (element.getAttribute("id").contains("mark")) {
					Matcher matcher = mXYMarkPattern.matcher(element.getAttribute("id"));
					if (matcher.find()) {
						if (matcher.group(1).compareTo("x")==0)
							mXTicks[Integer.parseInt(matcher.group(2))] = element;
						if (matcher.group(1).compareTo("y")==0)
							mYTicks[Integer.parseInt(matcher.group(2))] = element;
					}
				}
			}
		}
	}

	/**
	 * Call this function to update the plot.
	 * @param val the new value which the plot should be updated with
	 * @return a VisualizationUpdate that can be passed to the UIManager for execution
	 */
	public synchronized VisualizationUpdate updatePlot (final double val) {
		// Add new value to dataset (Hack: Multiply by thousand to allow for sub-second resolution)
		mData.addMeasurement(new CompositeMeasurement(val, System.currentTimeMillis()*1000));

		// Construct and fill up a VisualizationUpdate
		VisualizationUpdate update = new VisualizationUpdate(mName);

		update.addCommand(mLastValue.getAttribute("id"), mDataFormat.format(val));
		updatePlot(update);
		return update;
	}

	/**
	 * Computes the new text to be applied to the x and y marks of the plot. Adds the appropriate commands to
	 * the VisualizationUpdate that is passed as an argument
	 */
	private void setXYMarks (double minx, double maxx, double miny, double maxy, VisualizationUpdate update) {
		double xunit = ((maxx-minx)/12)/1000;
		for (int i = 0 ; i<mXTicks.length ; i++ ) {
			long millis = (long) ((xunit*3 + xunit*i*4 + minx));
			Date date = new Date(millis);
			update.addCommand(mXTicks[i].getAttribute("id"), mTimeFormat.format(date));
		}
		double yunit = ((maxy-miny)/5);
		for (int i=0;i<mYTicks.length;i++) {
			update.addCommand(mYTicks[i].getAttribute("id"), mYAxisFormat.format(miny + yunit*(i+1)));
		}
	}

	/**
	 * Updates the plot by calculating the new line polygon. Adds the appropriate commands to
	 * the VisualizationUpdate that is passed as an argument
	 * @param update
	 */
	private void updatePlot(VisualizationUpdate update){
		if (mData.getFirstMeasurement() == null){
			return;
		}
		
		mPos.clear();
		// zero point
		final double t0 = mData.getFirstMeasurement().timestampMicros;
		for (CompositeMeasurement m : mData.getAll()) {
			mPos.add(new double[]{m.timestampMicros-t0, m.value});
		}
		if (mPos.size() <= 1) return;
		// Scale
		final double max_x = mPos.get(mPos.size()-1)[0];
		final double x_scale = width_px/Math.abs(max_x - 0);

		final double max_y = (mData.getMaxValue() + (mData.getMaxValue() * 0.2));
		final double min_y = ((mData.getMinValue() == max_y) ? max_y + max_y*0.2:mData.getMinValue()-(mData.getMinValue()*0.2));

		final double y_scale = height_px/Math.abs(max_y - min_y);
		// Scaling transformation
		for (int i=0;i<mPos.size();i++) {
			mPos.get(i)[0] = mPos.get(i)[0]*x_scale;
			mPos.get(i)[1] = mPos.get(i)[1]*y_scale;
		}

		// Relative coordinates
		double offset =  -(min_y*y_scale);
		for (int i=0;i<mPos.size();i++) {
			mPos.get(i)[1] += offset;
		}

		// Transformation into absolute coordinates
		for (int i=0;i<mPos.size();i++) {
			mPos.get(i)[0] += x_0;
			mPos.get(i)[1] = y_0 - mPos.get(i)[1];
		}

		// Conversion to polygon string
		String poly = "m "+mPos.get(0)[0] + "," +mPos.get(0)[1];
		for (int i=1;i<mPos.size();i++) {
			poly += " " + (mPos.get(i)[0]-mPos.get(i-1)[0]) +","+((1*(mPos.get(i)[1]-mPos.get(i-1)[1])));
		}

		update.addCommand(mLine.getAttribute("id"), "d", poly);
		setXYMarks(t0, max_x+t0, min_y, max_y, update);
	}
}
